package com.mo.tools.excel;


import cn.hutool.core.lang.Assert;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisStopException;
import com.alibaba.excel.read.metadata.holder.ReadSheetHolder;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.beust.jcommander.internal.Maps;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.springframework.data.redis.core.RedisTemplate;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.io.FileOutputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author:miaoshaoxuan
 * @Description:
 * @Date 2021/5/12
 */
@Slf4j
@Getter
@Setter
public abstract class ExcelImportListener<T> extends AnalysisEventListener<T> {

    /**
     * Excel总条数阈值
     */
    private static final Integer BATCH_SIZE = 1000;

    /**
     * 错误信息
     */
    private static final String ERROR_MES = "错误信息";

    /**
     * 验证成功数据
     */
    private final ThreadLocal<List<Map<Integer, String>>> HEAD_LIST_TL = new InheritableThreadLocal<>();

    /**
     * 验证成功数据
     */
    private final ThreadLocal<List<T>> SUCCESS_LIST_TL = new InheritableThreadLocal<>();

    /**
     * 验证失败数据
     */
    private final ThreadLocal<Map<T, String>> ERROR_LIST_TL = new InheritableThreadLocal<>();

    /**
     * 已保存总数
     */
    private final ThreadLocal<Long> SAVE_TOTAL_COUNT_TL = new InheritableThreadLocal<>();

    /**
     * 已保存总数
     */
    private final ThreadLocal<Long> SAVE_BATCH_COUNT_TL = new InheritableThreadLocal<>();

    /**
     * 用户id
     */
    private String userId;

    /**
     * 链路id
     */
    private String traceId;

    /**
     * 子类Listener对象
     */
    private ExcelImportListener realExcelImportListener;

    public ExcelImportListener(String userId, String tracId) {
        this.userId = userId;
        this.traceId = tracId;
    }

    /**
     * 读取数据
     *
     * @param data
     * @param context
     */
    @Override
    public void invoke(T data, AnalysisContext context) {
        List<T> dataList = SUCCESS_LIST_TL.get();
        Map<T, String> errorMap = ERROR_LIST_TL.get();
        dataList = dataList == null ? new ArrayList<>() : dataList;
        errorMap = errorMap == null ? new LinkedHashMap<>() : errorMap;

        try {
            // 参数注解验证
            checkValidation(data, context);
            try {
                // 数据处理逻辑
                boolean handleStatus = checkWrapData(data, getHeadList());
                if (handleStatus) {
                    dataList.add(data);
                } else {
                    String errorMsg = getErrorMsg(traceId, data, context, "数据校验失败");
                    errorMap.putIfAbsent(data, errorMsg);
                }
            } catch (ExcelException e) {
                String errorMsg = getErrorMsg(traceId, data, context, e.getMessage());
                errorMap.put(data, errorMsg);
            } catch (Exception e) {
                String errorMsg = getErrorMsg(traceId, data, context, "数据校验失败");
                errorMap.put(data, errorMsg);
            }
            if (dataList.size() >= BATCH_SIZE || !hasNext(context)) {
                Long saveTotalCount = SAVE_TOTAL_COUNT_TL.get();
                Long saveBatchCount = SAVE_BATCH_COUNT_TL.get();
                // 按批次处理数据
                handleData(dataList);
                saveTotalCount = saveTotalCount == null ? 0L : saveTotalCount;
                saveTotalCount += dataList.size();
                SAVE_TOTAL_COUNT_TL.set(saveTotalCount);
                saveBatchCount = saveBatchCount == null ? 0L : saveBatchCount;
                saveBatchCount++;
                SAVE_BATCH_COUNT_TL.set(saveBatchCount);

                // 保存处理进度 条数
                String key = userId + "_" + realExcelImportListener.getClass().getSimpleName() + "_" + traceId;
                RedisTemplate<String, String> redisTemplate = EasyExcelUtils.getRedisTemplate();
                redisTemplate.opsForValue().set(key, "saveBatchCount", 1, TimeUnit.DAYS);
                log.info("traceId[{}] ExcelImportListener 导入中 导入类[{}] 导入批次[{}] 已导入总数[{}]", traceId, this.getClass().getSimpleName(), saveBatchCount + 1, saveTotalCount + 1);
            }

            SUCCESS_LIST_TL.set(dataList);
            ERROR_LIST_TL.set(errorMap);

            // 解析错误了1000条还没有保存成功，则任务整个excel数据都是错误
            if (errorMap.size() >= BATCH_SIZE && SAVE_TOTAL_COUNT_TL.get() == null) {
                log.error("traceId[{}] excel解析失败 前[{}]行都是错误数据", traceId, errorMap.size());
                throw new ExcelAnalysisStopException("excel解析失败");
            }

        } catch (Exception e) {
            String errorMsg = getErrorMsg(traceId, data, context, "导入excel数据错误");
            log.error(errorMsg, e);
        }

    }

    /**
     * 获取成功数据
     *
     * @return
     */
    private List<T> getSuccessList() {
        return SUCCESS_LIST_TL.get();
    }

    /**
     * 获取成功数据
     *
     * @return
     */
    private Map<T, String> getErrorMap() {
        return ERROR_LIST_TL.get();
    }

    /**
     * 增量保存总数
     *
     * @return
     */
    private Long getSaveTotalCount() {
        return SAVE_TOTAL_COUNT_TL.get();
    }

    /**
     * 获取表头
     *
     * @return
     */
    private List<Map<Integer, String>> getHeadList() {
        return HEAD_LIST_TL.get();
    }

    /**
     * 获取报错excel表头
     *
     * @return
     */
    private List<List<String>> getErrorHeadList() {
        List<Map<Integer, String>> mapList = HEAD_LIST_TL.get();
        Map<Integer, List<String>> headMap = Maps.newLinkedHashMap();
        for (Map<Integer, String> rowMap : mapList) {
            AtomicInteger i = new AtomicInteger(0);
            rowMap.forEach((k, v) -> {
                List<String> list = headMap.getOrDefault(k, new ArrayList<String>());
                list.add(v);
                headMap.put(k, list);
                if (i.get() == rowMap.size() - 1) {
                    List<String> errList = headMap.getOrDefault(k + 1, new ArrayList<String>());
                    errList.add(ERROR_MES);
                    headMap.put(k + 1, errList);
                }
                i.getAndIncrement();
            });
        }
        return new ArrayList<>(headMap.values());
    }

    /**
     * 处理数据逻辑
     *
     * @param dataList
     */
    abstract protected void handleData(List<T> dataList);

    /**
     * 数据处理后置操作
     *
     * @param successList
     * @param errorMap
     * @param saveTotalCount
     */
    abstract protected void handleCompletedAfter(List<T> successList, Map<T, String> errorMap, Long saveTotalCount);

    /**
     * 验证并包装数据 成功返回true 失败返回false 失败最好抛出ExcelException，给出明确错误
     *
     * @param data     处理后的数据
     * @param headList
     * @return
     */
    protected boolean checkWrapData(T data, List<Map<Integer, String>> headList) throws ExcelException {
        return true;
    }


    /**
     * 解析完成后
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        try {
            // 后置处理
            handleCompletedAfter(getSuccessList(), getErrorMap(), getSaveTotalCount());
            // 完成后错误处理
            doAfterErrorHandle(context, getErrorMap());
            // 更新限流
            updateRateLimit(traceId, userId, realExcelImportListener.getClass().getSimpleName());
        } catch (Exception e) {
            log.error("traceId[{}] 完成导入处理异常 ExcelImportListener 导入失败 导入类[{}]", traceId, this.getClass().getSimpleName(), e);
        } finally {
            // 清除缓存
            clearCache();
        }
        log.info("traceId[{}] ExcelImportListener 导入完成 导入类[{}] 导入总数[{}]", traceId, this.getClass().getSimpleName(), getSaveTotalCount());
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        // 验证和包装表头，如自定义表头处理
        try {
            checkWrapHead(headMap, context);
            List<Map<Integer, String>> headList = HEAD_LIST_TL.get();
            headList = headList == null ? new ArrayList<>() : headList;
            headList.add(headMap);
            HEAD_LIST_TL.set(headList);
        } catch (Exception e) {
            log.error("traceId[{}] 处理表头失败 ExcelImportListener 导入类[{}]  第[{}], 表头[{}]", traceId, this.getClass().getSimpleName(), context.readRowHolder().getRowIndex() + 1, headMap);
        }
    }

    /**
     * 验证和包装表头，如自定义表头处理
     *
     * @param headMap
     * @param context
     */
    private void checkWrapHead(Map<Integer, String> headMap, AnalysisContext context) {
    }

    /**
     * 完成后置处理
     *
     * @param context
     * @param errorMap
     */
    private void doAfterErrorHandle(AnalysisContext context, Map<T, String> errorMap) {
        if (!ObjectUtils.isEmpty(errorMap)) {
            ReadSheetHolder readSheetHolder = context.readSheetHolder();
            String sheetName = readSheetHolder.getSheetName();
            sheetName = ObjectUtils.isEmpty(sheetName) ? "Sheet1" : sheetName;
            List<List<Object>> errDataList = Lists.newArrayList();
            try {
                FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\siv\\Desktop\\Tmp\\test\\error.xlsx");
                errorMap.forEach((key, msg) -> {
                    Map<String, Object> dataMap = objectToMap(key);
                    Collection<Object> values = dataMap.values();
                    ArrayList<Object> dataList = new ArrayList<>(values);
                    dataList.add(msg);
                    errDataList.add(dataList);
                });
                EasyExcel.write(fileOutputStream)
                        .head(getErrorHeadList())
                        .autoCloseStream(Boolean.TRUE)
                        .autoTrim(Boolean.TRUE)
                        .excelType(ExcelTypeEnum.XLSX)
                        .registerWriteHandler(new CustomCellWriteWidthHandle())
                        .sheet(sheetName)
                        .doWrite(errDataList)
                ;
            } catch (Exception e) {
                log.error("traceId[{}] 导入后置处理失败 ExcelImportListener 导入类[{}]", traceId, this.getClass().getSimpleName(), e);
                throw new RuntimeException("导入失败");
            }
        }
    }

    public static Map<String, Object> objectToMap(Object obj) {
        if (ObjectUtils.isEmpty(obj)) {
            return new HashMap<>();
        }
        ObjectMapper mapper = new ObjectMapper();
        return mapper.convertValue(obj, Map.class);
    }

    private void clearCache() {
        SUCCESS_LIST_TL.remove();
        ERROR_LIST_TL.remove();
        SAVE_TOTAL_COUNT_TL.remove();
        SAVE_BATCH_COUNT_TL.remove();
        HEAD_LIST_TL.remove();
    }

    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {

    }

    /**
     * 参数字段验证
     *
     * @param data
     * @param context
     */
    private void checkValidation(T data, AnalysisContext context) {
        try {
            if (data != null) {
                HibernateValidatorConfiguration configure = Validation.byProvider(HibernateValidator.class).configure();
                ValidatorFactory validatorFactory = configure.failFast(false).buildValidatorFactory();
                // 根据validatorFactory拿到一个Validator
                Validator validator = validatorFactory.getValidator();
                Set<ConstraintViolation<T>> result = validator.validate(data);
                // 存在验证失败的数据
                if (!ObjectUtils.isEmpty(result)) {
                    StringBuilder stringBuilder = new StringBuilder();
                    for (ConstraintViolation<?> constraintViolation : result) {
                        try {
                            T object = (T) constraintViolation.getLeafBean();
                            ExcelProperty excelProperty = object.getClass().getDeclaredField(constraintViolation.getPropertyPath().toString()).getAnnotation(ExcelProperty.class);
                            String[] value = excelProperty.value();
//                        String header = String.join(",", value);
//                        stringBuilder.append(header);
                            stringBuilder.append(",");
                            stringBuilder.append(constraintViolation.getMessage());
                        } catch (NoSuchFieldException e) {
                            log.error(e.getMessage());
                        }
                    }
                    stringBuilder.deleteCharAt(stringBuilder.indexOf(","));
                    String errorMsg = getErrorMsg(traceId, data, context, stringBuilder.toString());
                    Map<T, String> errorMap = ERROR_LIST_TL.get();
                    errorMap = errorMap == null ? new LinkedHashMap<>() : errorMap;
                    errorMap.put(data, errorMsg);
                    ERROR_LIST_TL.set(errorMap);
                }

            }
        } catch (Exception e) {
            String errorMsg = getErrorMsg(traceId, data, context, "验证注解参数异常");
            log.error("导入失败 {}", errorMsg, e);
        }

    }

    /**
     * 获取错误信息
     *
     * @param traceId
     * @param data
     * @param context
     * @param errorMsg
     * @return
     */
    private String getErrorMsg(String traceId, T data, AnalysisContext context, String errorMsg) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("第").append(context.readRowHolder().getRowIndex() + 1).append("行, ");
        stringBuilder.append("[");
        stringBuilder.append(errorMsg);
        stringBuilder.append("]");
        log.error("traceId[{}] 导入失败 ExcelImportListener 导入类[{}]  失败数据[{}] 错误信息[{}]", traceId, this.getClass().getSimpleName(), data, stringBuilder);
        return stringBuilder.toString();
    }

    /**
     * 限流处理
     */
    private static void updateRateLimit(String traceId, String userId, String clazzName) {
        Assert.notBlank(traceId, "链路id不能为空");
        Assert.notBlank(userId, "用户id不能为空");
        Assert.notBlank(clazzName, "Listener 类名不能为空");
        String key = userId + "_" + clazzName;
        try {
            RedisTemplate<String, String> redisTemplate = EasyExcelUtils.getRedisTemplate();
            String value = redisTemplate.opsForValue().get(key);
            if (!ObjectUtils.isEmpty(value)) {
                int count = Math.max(Integer.parseInt(value) - 1, 0);
                redisTemplate.opsForValue().set(key, String.valueOf(count), 2, TimeUnit.HOURS);

            }

        } catch (Exception e) {
            log.error("traceId[{}] 导入失败 ExcelImportListener 导入类[{}]  更新限流异常", traceId, clazzName,e);
        }

    }


}

